package com.mango.request;

import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.util.Log;

import com.mango.volley.AuthFailureError;
import com.mango.volley.NetworkResponse;
import com.mango.volley.Request;
import com.mango.volley.Response;
import com.mango.volley.VolleyError;
import com.mango.volley.toolbox.HttpHeaderParser;
import com.mango.wall.BuildConfig;

import org.json.JSONException;
import org.json.JSONObject;

import java.io.UnsupportedEncodingException;
import java.util.Map;

@SuppressWarnings("deprecation")
public abstract class ReqMango<T> extends Request<T> {
    private static final String LOG_TAG = "ManGo_Req";
    private static final String LOG_DIVIDER1 = "==================== ACTION ====================";
    private static final String LOG_DIVIDER2 = "==================== HEADER ====================";
    private static final String LOG_DIVIDER3 = "==================== PARAMS ====================";
    private static final String LOG_DIVIDER4 = "==================== RESULT ====================";
    private static final String LOG_DIVIDER5 = "==================== ERRORS ====================";

    private static final String SERVER_URL = "http://api2.mangolm.com/";
    private static final String PARAMS_DIV = "?";
    private static final String PARAMS_AND = "&";
    private static final String PARAMS_EQL = "=";
    private static final String PARAMS_TOKEN = "apiToken";

    private static String buildRequestUrl(String action, Object[] queries) {
        StringBuilder builder = new StringBuilder(SERVER_URL);
        builder.append(action);

        int size = queries.length / 2;
        StringBuilder source = new StringBuilder();

        for (int i = 0; i < size; i++) {
            Object k = queries[i * 2];
            Object v = queries[i * 2 + 1];

            if (k == null)
                source.append(v);
            else {
                if (builder.indexOf(PARAMS_DIV) < 0)
                    builder.append(PARAMS_DIV);
                else
                    builder.append(PARAMS_AND);
                if (k instanceof String) {
                    source.append(v);
                    builder.append(k);
                } else
                    builder.append((char[]) k);
                builder.append(PARAMS_EQL).append(v);
            }
        }

        builder.append(PARAMS_AND)
                .append(PARAMS_TOKEN).append(PARAMS_EQL)
                .append(Algorithm.MD5Encrypt(source.toString()));

        return builder.toString();
    }

    private final Object mLock = new Object();

    private String mJsonParams;
    private Response.Listener<T> mListener;

    public ReqMango(String action, Object[] queries,
                    @Nullable Response.Listener<T> listener,
                    @Nullable Response.ErrorListener errorListener) {
        super(Method.POST, buildRequestUrl(action, queries), errorListener);

        mListener = listener;
    }

    protected abstract String getJsonParams() throws JSONException;

    protected abstract T parseJsonResult(String json) throws JSONException;

    @Nullable
    @Override
    protected final Map<String, String> getParams() throws AuthFailureError {
        return super.getParams();
    }

    @Nullable
    @Override
    protected final Map<String, String> getPostParams() throws AuthFailureError {
        return super.getPostParams();
    }

    @Override
    protected final String getPostParamsEncoding() {
        return super.getPostParamsEncoding();
    }

    @Override
    public final byte[] getPostBody() throws AuthFailureError {
        return super.getPostBody();
    }

    @Override
    public final String getPostBodyContentType() {
        return super.getPostBodyContentType();
    }

    @Override
    public final String getBodyContentType() {
        return "application/json; charset=" + getParamsEncoding();
    }

    @Override
    public final byte[] getBody() {
        try {
            mJsonParams = getJsonParams();
            if (mJsonParams == null) return null;
            return mJsonParams.getBytes(getParamsEncoding());
        } catch (JSONException exception) {
            mJsonParams = Log.getStackTraceString(exception);
            return null;
        } catch (UnsupportedEncodingException exception) {
            mJsonParams = Log.getStackTraceString(exception);
            return null;
        }
    }

    @Override
    public final void cancel() {
        super.cancel();
        synchronized (mLock) {
            mListener = null;
        }
    }

    @Override
    protected final VolleyError parseNetworkError(VolleyError volleyError) {
        logRequest(null, Log.getStackTraceString(volleyError));
        return super.parseNetworkError(volleyError);
    }

    @Override
    protected final Response<T> parseNetworkResponse(NetworkResponse response) {
        String data = new String(response.data);
        try {
            T result = parseJsonResult(data);
            logRequest(data, null);
            return Response.success(result,
                    HttpHeaderParser.parseCacheHeaders(response));
        } catch (JSONException exception) {
            logRequest(data, Log.getStackTraceString(exception));
            return Response.error(new VolleyError(exception));
        }
    }

    @Override
    protected final void deliverResponse(T response) {
        Response.Listener<T> listener;
        synchronized (mLock) {
            listener = mListener;
        }
        if (listener != null) {
            listener.onResponse(response);
        }
    }

    private void logRequest(String result, String error) {
        if (!BuildConfig.DEBUG) return;

        StringBuilder builder = new StringBuilder();
        builder.append('　').append('\n');

        builder.append(LOG_DIVIDER1).append('\n');
        builder.append(getUrl()).append('\n');

        try {
            Map<String, String> headers = getHeaders();
            if (!headers.isEmpty())
                builder.append(LOG_DIVIDER2).append('\n');
            for (Map.Entry<String, String> entry : headers.entrySet()) {
                builder.append(entry.getKey()).append('=');
                builder.append(entry.getValue()).append('\n');
            }
        } catch (Exception exception) {
            // Do nothing.
        }

        if (!TextUtils.isEmpty(mJsonParams)) {
            builder.append(LOG_DIVIDER3).append('\n');
            try {
                builder.append(new JSONObject(mJsonParams)
                        .toString(1)).append('\n');
            } catch (Exception exception) {
                builder.append(mJsonParams).append('\n');
            }
        }

        if (!TextUtils.isEmpty(result)) {
            builder.append(LOG_DIVIDER4).append('\n');
            try {
                builder.append(new JSONObject(result)
                        .toString(1)).append('\n');
            } catch (Exception exception) {
                builder.append(result).append('\n');
            }
        }

        if (!TextUtils.isEmpty(error)) {
            builder.append(LOG_DIVIDER5).append('\n');
            builder.append(error).append('\n');
            Log.e(LOG_TAG, builder.toString());
        } else {
            Log.d(LOG_TAG, builder.toString());
        }
    }
}
